﻿import flash.display.BitmapData
import flash.geom.*

class Game
{
	//pointers
	private var my_world:World = null;
	private var my_snake:Snake = null;
	private var my_p_root:MovieClip = null; // store pointer to root
	private var my_overlay:MovieClip = null;
	
	//data
	private var score:Number = 0;
	private var current_level:Number = 0; 
	private var level_data:Array; //container for indexed level data struct
	
	//game state logic
	private var lives:Number = 0;
	private var gameon:Boolean = false; //flag if the game is stopped for any event
	private var alive:Boolean = false; //flag if event is a death
	private var gamestate:String; //container for state
	private var state:Array = new Array("intro", "play", "outro"); //this array acts as an enum .: the game can only be in one of three exclusive states
	
	//variables for death / game over sequence
	private var G:Number = 16; //gravity
	private var M:Number = 1.4; //mass
	private var a:Number; //acceleration
	private var FR:Number = 0.9; //friction
	private var start_death:Boolean = false; //flag when the death sequence begins
	private var end_death:Boolean = false; //flag when the death sequence ends
	
	//variables for game over sequence
	private var start_outro:Boolean = false; //flag outro begins
	private var end_outro:Boolean = false; //flag outro ends
	private var tiles:Array = null; // will store the bitmap tiles of the screen on game over
	
	//variables for level-up sequence
	private var level_up_del:Number = 0; //wait for n number of music loops to complete before leveling up
	private var start_level_clear:Boolean = false;//flag if sequence has started
	
	//variables for screen effects
	
	function Game(p_root:MovieClip,level_struct:Array,start_level:Number,start_lives:Number)
	{
		//trace("new game created");
		my_p_root = p_root;
		//initialise level data
		level_data = level_struct.slice(); // copy the data_struct as a class attribute
		lives = start_lives; // to be passed in...
	
		//start new game
		new_game(start_level); //start a new game on level 0 
	}
	public function new_game(level:Number):Void
	{
		
		//(re)initialise intro / outro flags
		end_death = start_death = false;
		
		//reset level data
		my_p_root.set_len(level_data[level].sig);
		my_p_root.set_bpm(level_data[level].tempo);
		my_p_root.set_bgm(level_data[level].bg_loop);
		
		current_level = level; //store the current level
		//set up the sequencer according to data file
		
		//initiate a game environment (level) - overwrites previous reference (automatic garbage collection) 
		my_world = new World(my_p_root,level_data[level].colours,level_data[level].colour_dictionary, level_data[level].t_seq, level_data[level].spawn_prob, level_data[level].max_spawn);
		//instantiate a snake
		my_snake = new Snake(my_p_root,my_world,this,level_data[level].sig,100);
		//instantiate a graphic layer to sit on top of the play-field
		my_overlay = my_p_root.attachMovie("display","display0",5000,{_x:Stage.width * 0.5, _y:Stage.height * 0.5});
		alive = true;
		gamestate = state[0]; //intro state
		//gameon = true; //flag that the game is playing
		update_display();
	}
	public function update_game(Void):Void
	{
		if (gameon) //if game is on continue updates
		{
			my_world.update_world();
			my_snake.update_snake();
		}
		else if (!gameon) //otherwise pause updates and find out reason for stopping game
		{
			//trace("game paused");
			
			if(!alive)//--------------------------------------------------------------------------------------------
			{
				//trace ("player died");
				
				my_p_root._quality = "LOW"; //speed up render
				
				my_overlay.visibility = false; //hide overlay
				
				var tail_copy:Array = my_snake.get_tail_array(); //copy of all objects
				
				if (!start_death) //initialise death sequence---------------------------------------------------
				{
					trace ("death sequence started");
					
					a = 40; //init acceleration
					
					//calculate angles and cahce as bitmaps for speed
					for (var i:Number = 0; i < tail_copy.length; i++)
					{
						tail_copy[i].t = Math.atan2(tail_copy[i]._y - 480, tail_copy[i]._x - 300);//angle to middle of bottom edge of screen
						//tail_copy[i].cacheAsBitmap = true; // speed boost ? !!!!!!!!!!!!!!!
					}
					for (var i:Number = 0; i < my_p_root.seq.length; i++)//empty the sequencer
					{
						my_p_root.seq[i] = undefined//empty the sequencer
					}
					//play the sound
					my_p_root.die.start(0,1);
					//flag start of sequence
					start_death = true;
				}
			
				//do death sequence------------------------------------------------------------------------------
				
				if (!end_death)
				{
					
					//physics of gravity counteracting an initial force
					a *= FR; //acceleration slows with friction
					var f:Number = M*a; //force is mass * acceleration
					f -= G; // gravity is a constant acting on initial force
					
					//apply to tail
					var highest_y:Number = 481; //used to find out if all objects are off the screen yet
					for (var i:Number = 0; i < tail_copy.length; i++)
					{
						tail_copy[i]._x += 1 * Math.cos(tail_copy[i].t); // move object on x plane by a constant
						tail_copy[i]._y += f * Math.sin(tail_copy[i].t); // move object on y plane by a trajectory
						
						if (tail_copy[i]._y < highest_y)
						{
							trace("highest y: " + highest_y); 
							highest_y = tail_copy[i]._y;
						}
					}
					
					//evaluate end condition--------------------------------------------------------------------------
					
					if (highest_y > 480) //if there are no objects on screen
					{
						trace("death sequence completed");
						//make a single clatter sound
						my_p_root.thump.start();
						end_death = true;
					}
				}
				else if (end_death) //if sequence end condition met-------------------------------------------------
				{
					if (lives <=0) //player has died and run out of lives
					{
						if (!start_outro)
						{
							trace("game over");
							
							my_p_root.kill_sequencer();//'break' the sequencer
							
							//play smash noise
							my_p_root.smash.start();
							
							//Initialise game over sequence----------------------------------------------------------------------------------
							
							//BETA!!!!
							var screen_grab:BitmapData = new BitmapData(Stage.width, Stage.height, false, 0x666666);
							var screen_holder:MovieClip = my_p_root.createEmptyMovieClip("a_screen_holder",my_p_root.getNextHighestDepth());
							var screen_matrix:Matrix = new Matrix();
							//screen_matrix.scale(0.5,0.5);
							//screen_matrix.translate(Stage.width/4, Stage.height/4);
							screen_grab.draw(my_p_root, screen_matrix);
							
							tiles = new Array(); //initialise the tile array
							var rows:Number = 10;
							var cols:Number = 10;
							var x_unit:Number = Stage.width / cols;
							var y_unit:Number = Stage.height / rows;
							var total:Number = rows*cols;
							for (var i:Number = 0; i<total; i++)
							{
								//current column and row
								var col:Number = Math.floor(1 / total * i * cols); //find appropriate column
								var row:Number = i % rows; // loop around rows
								
								//divide screen into tile bitmaps
								tiles[i] = my_p_root.createEmptyMovieClip("tile" + i, my_p_root.getNextHighestDepth());
								var tile_grab:BitmapData = new BitmapData(x_unit, y_unit, false, 0x000000);
								tile_grab.copyPixels(screen_grab, new Rectangle(col * x_unit, row * y_unit, x_unit, y_unit),new Point(0, 0));
								tiles[i].attachBitmap(tile_grab,1);
								tiles[i]._x = col * x_unit;
								tiles[i]._y = row * y_unit;
							}
							
							//clear objects
							clear_game();
							
							//initialise physics
							a = 40; //init acceleration
							for (var i:Number = 0; i < tiles.length; i++) //init tile angles
							{
								tiles[i].t = Math.atan2(tiles[i]._y - 480, tiles[i]._x - 300);//angle to middle of bottom edge of screen
							}
							
							start_outro = true;
						}
						
						//Begin Game over sequence----------------------------------------------------------------------------------
						
						if (!end_outro)
						{
							gotoAndStop("game_over");
							trace ("outro loop");
							//physics of gravity counteracting an initial force
							a *= FR; //acceleration slows with friction
							var f:Number = M*a; //force is mass * acceleration
							f -= G; // gravity is a constant acting on initial force
							
							//apply to tiles
							var highest_tile:Number = 481; //used to find out if all objects are off the screen yet
							//trace("highest tile reset: " + highest_tile);
							for (var i:Number = 0; i < tiles.length; i++)
							{
								//trace("i: " + i);
								tiles[i]._x += 1 * Math.cos(tiles[i].t); // move object on x plane by a constant
								tiles[i]._y += f * Math.sin(tiles[i].t); // move object on y plane by a trajectory
								//tiles[i]._alpha *= 0.9;
								
								if (tiles[i]._y < highest_tile)
								{
									trace("highest tile: " + highest_tile); 
									highest_tile = tiles[i]._y;
								}
							}
						
							//evaluate end condition--------------------------------------------------------------------------
							
							if (highest_tile > 480) //if there are no objects on screen
							{
								trace("game over sequence completed");
								end_outro = true;
							}
						}
						else if (end_outro)
						{
							//collect garbage...
							for (var i:Number = 0; i < tiles.length; i++) //init tile angles
							{
								tiles[i].removeMovieClip();
							}
							//reset game
							my_p_root.reset(); //kills sequencer deletes this world and then goes to menu
						}
					}
					else if (lives > 0) //player has died and has lives left
					{
						trace ("restarting game");
						//collect garbage...
						clear_game();
						new_game(current_level); //overwrite snake and world with new instances
					}
				}//------------------------------------------------------------------------------------------------
			}
			else if (alive)//-----------------------------------------------------------------------------------------
			{
				switch (gamestate)
				{
					case "intro" : //level is beginning 
					{
						trace("intro sequence");
						
						my_overlay.listen(); //update display
						
						//perform intro sequence
						
						var target_copy:Array = my_world.get_target_sequence();//copy target seq
						my_p_root.seq = target_copy;//play target sequence
						
						gamestate = state[1]; //main
						gameon = true;
						break;
					}
					case "play" : //game is in session .: player has paused the game...
					{
						trace("pause");
						//set pause status and get out clause
						gameon = true;
						break;
					}
					case "outro" : //player has completed the sequence...
					{
						if(!start_level_clear)
						{
							my_overlay.level_clear(); //update display
							trace("outro sequence");
							//do outro sequence
							start_level_clear = true;
						}
						
						if (my_p_root.stp == 0) //on the next beat...
						{
							//increment delay
							level_up_del++;
							
							trace("counting down to next level: " + level_up_del);
							
							if (level_up_del >= 7) // 8 loops
							{
								//update level
								trace("next entry in datafile: " + level_data[current_level+1]);
								if(level_data[current_level++] != undefined)
								{
									//collect garbage...
									clear_game();
									new_game(current_level); //overwrite snake and world with new instances
									break;
								}
								else
								{
									//run out of level data
									trace("no more levels!!")
									my_p_root.game_complete();
								}
							}
						}
					}
				}
			}
		}
	}
	public function death(Void):Void
	{
		decrement_lives();
		gameon = false
		alive = false;
	}
	public function win(Void):Void
	{
		gamestate = state[2]; //outro
		gameon = false;
	}
	private function decrement_lives(Void):Void // remove one life from the players available lives
	{
		lives--;
		update_display();
	}
	private function update_display(Void):Void //update graphics on root layer
	{
		my_p_root.lives = lives;
		my_p_root.level = current_level + 1;//remember that the array is 0 based
		my_p_root.score = score;
	}
	private function clear_game(Void):Void
	{
		my_snake.clear_snake();//remove movieclips from snake and world
		my_world.clear_world();
		my_overlay.removeMovieClip(); //free up the object memory
		delete my_overlay;
		delete my_snake;//free up the object memory for all mc's
		delete my_world; //free up the object memory for all mc's
	}
	public function clear_me(Void):Void//wrapper for main reset and quit funcitons
	{
		clear_game();
	}
}